Eine Einführung
AE Psychologische Methodenlehre, Philipps-Universität Marburg
2023-06-23
https://bit.ly/netz-work-material
Netzwerktheorie:
graphicalVAR: idiographische NetzwerkemlVAR: Multilevel, ermöglicht Visualisierung von HeterogenitätGIMME: idionomischmgm: Mixed models & zeitlich variierendDSEM (MPlus): komplexe Messmodelle, sehr flexibelBGGM. Bayesianische Methodenpsychonetrics: übergreifende Architekturdynr: Dynamische SystemeHintergrund:
für Modellfitting: erste 30 IDs
Sehr wichtiger Schritt
Einfluss von Datenvorverarbeitung auf Ergebnisse wird oftmals unterschätzt!
Abhängig vom Package
graphicalVAR: Dataframe im Long-Format (jede Beobachtung eine Zeile)GIMME: Listenformat oder individuelle Datenfiles in einem OrdnerAbhängig vom Package:
graphicalVAR: Akzeptiert keine fehlenden Daten. Imputation vorab, etwa univariat über Kalman-Filter aus (na_kalman aus tsImpute)Hoch relevant für die Interpretation!
Mehrere Möglichkeiten:
Explizite Modellierung über continuous-time Modellierung (ctsem)
Auslassen von Effekten über die Nacht in graphicalVAR oder GIMME
Cubic Spline Interpolation (s. Fisher et al., 2017)
Ignorieren 😓
graphicalVARund GIMME: Normalverteilte VariablengraphicalVAR (Espkamp, 2018) ermöglicht idiographische NetzwerkanalysenInterpretation von Heterogenität
Netzwerke sehen durch Schätzunsicherheit oft heterogener aus, als sie tatsächlich sind (Hoekstra et al., 2022; Siepe et al., in prep.)
fit <- graphicalVAR(
data = NULL, # Datensatz
nLambda = 50, # Anzahl von LASSO Parametern, die getestet wird
gamma = 0.5, # EBIC Hyperparameter
scale = TRUE, # z-standardisieren (wichtig für LASSO!)
vars = NULL, # Vektor mit Variablennamen
beepvar = "beep", # Beepvariable
dayvar = "day", # Tagesvariable
idvar = "id" # ID der Person
)boot oder dem bootnet-PackageVorab gestellte Fragen:
Wichtigkeit von nLambda?
Umgang mit EBIC Hyperparameter \(\gamma\)
Einfluss von Detrending
Lags bei graphicalVAR
nLambda?A: Anzahl unterschiedlicher LASSO Regularisierungsparameter. Typischerweise wird default von 50 verwendet
A: Wird gerade bei wenigen Daten oftmals auf 0 gesetzt (s. Mansueto et al, 2022). Dann wird EBIC zum normalen BIC. Abhängig vom Ziel der Untersuchung (Spezifizität vs. Sensitivität).
A: Scheinzusammenhänge zwischen Variablen, die eigentlich nicht miteinander zusammenhängen
graphicalVARA: Bei Spezifikation von Beep-Variable wird der Effekt vom letzten Beep eines Tages auf den nächsten nicht geschätzt
graphicalVAR: rein idiographisch (kann aber auf Multilevel erweitert werden)graphicalVAR: Schätzt erst temporal, dann contemporaneousGIMME: gerichtet zwischen beobachteten VariablengraphicalVAR: ungerichtet zwischen Residuenhybrid-GIMME (Luo et al., 2023): Verbindet beides! Braucht aber auch mehr DatenZusatzinformation
Die zugrundeliegenden Modelle können mathematisch ineinander transformiert werden (Luo et al., 2023)
Takeaway-Zusammenfassung:
Ein “leeres” Modell für temporal und contemporaneous Zusammenhänge wird auf die Daten angepasst
Gruppe: Iterative Suche nach Verbindungen (anhand von Modifikationsindizes), die Modellfit für bestimmten Teil (etwa 75%) des Samples signifikant (nach Korrektur \(\alpha = 0.05/n\)) verbessern
Pruning von nicht länger signifikanten Pfaden
Subgruppe: Ähnlichkeiten zwischen Individuen werden gesucht anhand Größe der geschätzten Effekte, daraus wird eine sog. Adjacency Matrix kreiert
Walktrap-Algorithmus, um “communities” zu identifizieren
In den entstandenen Subgruppen: Suche nach verbessernden Pfaden für bestimmten Teil (etwa 51%) der Subgruppe (nach Korrektur \(\alpha = 0.05/n\))
Pruning von nicht länger signifikanten Pfaden
Individuell: Suche nach verbessernden Pfaden (\(\alpha = 0.01\)), bis “exzellenter Fit” erreicht ist (RMSEA < .05, SRMR < .05, CFI > .95, NNFI > .95).
\[ \eta_{i,t} = (\color{Red}{A_i} + \color{Blue}{A^S_{i,k}} + \color{orange}{A^g_i})\eta_{i,t} + (\color{Red}{\phi_i} + \color{Blue}{\phi^s_{i,k}} + \color{orange}{\phi^g_i})\eta_{i,t-1} + \zeta_{i,t} \]
\(\eta_{i,t}\): Daten von Individuum \(i\) zum Zeitpunkt \(t\)
\(A\): Contemporaneous Effekte
\(\phi\): Temporale Effekte (VAR-1 Modell)
Orange: Gruppeneffekte
Blau: Subgruppeneffekte
Rot: Individuelle Effekte
\(\zeta_{i,t}\): Residuum von Individuum \(i\) zum Zeitpunkt \(t\)
fit <- gimmeSEM(
data = NULL, # Datenfile
out = NULL, # Outputordner
ar = TRUE, # Autoregressive Effekt schätzen (default)
plot = TRUE, # Plotten?
subgroup = TRUE, # Subgruppen schätzen?
hybrid = FALSE, # directed & undirected contemporaneous
groupcutoff = .75, # Gruppencutoff
subcutoff = .51, # Subgruppencutoff
...
)Vorab gestellte Fragen:
NA für die Nacht, damit der Nachteffekt nicht geschätzt wirdEffektive Stichprobengröße
Bei Weglassen von Nachteffekten verringert sich die effektive Stichprobengröße!
Wir treffen die notwendigen Vorbereitungen und laden die relevanten Daten. Diese wurden bereits vorab in das notwendige Listenformat umgewandelt und etwas vorverarbeitet, Code dafür ist vorhanden.
file_list <- list.files(here::here("Anwendungs_Workshop/data/individual_files"),
full.names = TRUE)
data_list <- lapply(file_list, read.csv)
# Zeitvariable hinzufügen
data_list <- lapply(data_list, function(x){
x <- x |>
dplyr::mutate(time = dplyr::row_number())
})
saveRDS(data_list, here::here("Anwendungs_Workshop/data/data_list.RDS"))Jedes Individuum hat einen eigenen Eintrag in einer Liste.
Für bessere Übersicht: Deskriptive Statistiken aller Variablen:
skimr::skim()dplyr::glimpse()Hmisc::describe()Beispiel für Individuum 2:
| skim_type | skim_variable | n_missing | complete_rate | numeric.mean | numeric.sd | numeric.p0 | numeric.p25 | numeric.p50 | numeric.p75 | numeric.p100 | numeric.hist |
|---|---|---|---|---|---|---|---|---|---|---|---|
| numeric | Dominance | 3 | 0.9651163 | 2.9781928 | 6.2290361 | -9.95 | -1.44 | 2.71 | 7.95 | 16.90 | ▃▇▇▅▃ |
| numeric | Affiliation | 2 | 0.9767442 | -1.3853571 | 6.2989306 | -13.90 | -5.00 | -0.82 | 2.83 | 12.66 | ▃▅▇▃▂ |
| numeric | PosAff | 0 | 1.0000000 | 3.8459302 | 1.2070434 | 1.00 | 3.40 | 4.20 | 5.00 | 5.00 | ▂▂▂▃▇ |
| numeric | NegAff | 0 | 1.0000000 | 1.9906977 | 0.8584018 | 1.00 | 1.40 | 1.80 | 2.60 | 5.00 | ▇▃▂▁▁ |
| numeric | Stress | 0 | 1.0000000 | 0.5581395 | 1.8188856 | 0.00 | 0.00 | 0.00 | 0.00 | 9.00 | ▇▁▁▁▁ |
| numeric | Functioning | 0 | 1.0000000 | 0.4534884 | 0.6800861 | 0.00 | 0.00 | 0.00 | 1.00 | 2.00 | ▇▁▃▁▁ |
| numeric | time | 0 | 1.0000000 | 43.5000000 | 24.9699820 | 1.00 | 22.25 | 43.50 | 64.75 | 86.00 | ▇▇▇▇▇ |
standardize = TRUE)Über alle Personen hinweg:
# relevante Variablen
rel_vars <- c("Dominance", "Affiliation", "PosAff",
"NegAff", "Stress", "Functioning")
# Berechnen von Mean und SD
desc_list <- list()
for(p in 1:length(data_list)){
desc_list[[p]] <- data_list[[p]] |>
dplyr::summarize(across(all_of(rel_vars),
list(mean = mean, sd = sd), na.rm = TRUE))
}
df_desc <- bind_rows(desc_list, .id = "id")
df_desc |>
dplyr::summarize(across(everything(),
~ round(mean(.), 3))) |>
select(-id) |>
gt()| Dominance_mean | Dominance_sd | Affiliation_mean | Affiliation_sd | PosAff_mean | PosAff_sd | NegAff_mean | NegAff_sd | Stress_mean | Stress_sd | Functioning_mean | Functioning_sd |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.491 | 2.711 | 1.912 | 3.333 | 2.666 | 0.61 | 1.786 | 0.516 | 2.425 | 2.391 | 1.147 | 0.69 |
Für einzelne Individuen:
| id | Dominance_mean | Dominance_sd | Affiliation_mean | Affiliation_sd | PosAff_mean | PosAff_sd | NegAff_mean | NegAff_sd | Stress_mean | Stress_sd | Functioning_mean | Functioning_sd |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | -6.352 | 1.928 | -5.662 | 2.896 | 1.533 | 0.266 | 2.355 | 0.448 | 2.783 | 2.417 | 2.772 | 0.494 |
| 10 | 2.306 | 2.367 | 2.259 | 2.520 | 3.006 | 0.350 | 1.411 | 0.379 | 2.959 | 2.685 | 1.371 | 0.527 |
| 11 | 3.610 | 1.532 | 7.122 | 2.515 | 3.188 | 0.390 | 1.152 | 0.283 | 0.337 | 1.627 | 0.574 | 0.517 |
| 12 | 0.625 | 3.478 | 0.374 | 2.907 | 1.942 | 0.898 | 1.625 | 0.781 | 1.141 | 2.832 | 0.481 | 0.868 |
| 13 | 0.410 | 1.270 | 0.682 | 1.617 | 1.557 | 0.453 | 1.035 | 0.116 | 0.542 | 1.187 | 0.115 | 0.432 |
| 14 | 1.680 | 3.853 | 9.364 | 3.599 | 3.655 | 0.848 | 1.317 | 0.476 | 3.065 | 4.219 | 0.948 | 1.134 |
Visualisierung von fehlenden Daten: etwa mit naniar-Package
Wir detrenden einen linearen Effekt von Zeit:
# relevante Variablen
rel_vars <- c("Dominance", "Affiliation", "PosAff",
"NegAff", "Stress", "Functioning")
# Loopen über alle p Participants
for(p in 1:length(data_list)){
data_list[[p]] <- fn_detrend(data_list[[p]],
vars = rel_vars,
time_var = "time",
sig_only = FALSE)
}
# Zeitvariable wieder löschn
for(p in 1:length(data_list)){
data_list[[p]] <- subset(data_list[[p]], select = -c(time))
}lapply für die Arbeit mit Listen# lapply statt for loop
mean_list <- lapply(data_list, function(x){
# x: einzelnes Element von data_list
mean(x$Dominance, na.rm = TRUE)
})
# oder als Datensatz verwenden
df_data <- data_list |>
# mit ID abspeichern
tibble::enframe(name = "ID") |>
# in dataframe verwandeln
tidyr::unnest()
# zurück in Liste verwandeln
data_list_new <- split(df_data, df_data$ID)example_data <- gimme::simData
data_list_short <- data_list[1:30]
fit <- gimmeSEM(
data = data_list_short,
out = "Anwendungs_Workshop/output",
ar = TRUE, # Autoregressive Effekt schätzen (oftmals empfohlen)
plot = TRUE, # Plotten?
subgroup = TRUE, # Subgruppen schätzen?
hybrid = FALSE, # directed & undirected contemporaneous
groupcutoff = .75, # Gruppencutoff
subcutoff = .51, # Subgruppencutoff,
paths = NULL # vorgegebene Gruppenpfade
)
saveRDS(fit, "Anwendungs_Workshop/output/fit.RDS")Alternativ: Fertiges Modell laden (s. Codefile)
Datenstruktur anders
Achtung: Datenstruktur kann anders sein als z.B. in qgraph (Reihe: Outcome, Spalte: Prädiktor)
Hauptoutput: summaryFit.csv
Please specify a file id for individual plots. Otherwise, summary plot is presented.
summary_matrix <- read.csv(here("Anwendungs_Workshop/output/summaryPathCountsMatrix.csv"))
summary_matrix |>
gt()| Dominancelag | Affiliationlag | PosAfflag | NegAfflag | Stresslag | Functioninglag | Dominance | Affiliation | PosAff | NegAff | Stress | Functioning |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 30 | 3 | 0 | 1 | 0 | 0 | 0 | 9 | 13 | 2 | 5 | 2 |
| 1 | 30 | 1 | 0 | 1 | 0 | 0 | 0 | 11 | 9 | 4 | 1 |
| 0 | 1 | 30 | 2 | 0 | 2 | 8 | 7 | 0 | 0 | 3 | 2 |
| 1 | 2 | 0 | 30 | 2 | 1 | 2 | 6 | 5 | 0 | 0 | 1 |
| 0 | 1 | 1 | 2 | 30 | 0 | 3 | 2 | 1 | 30 | 0 | 1 |
| 0 | 0 | 1 | 0 | 1 | 30 | 2 | 2 | 8 | 3 | 7 | 0 |
Interpretation von Subgruppen
Es kann Subgruppen ohne gemeinsames Edge geben! Ähnlichkeit der Pfade allgemein.
GIMME als InputMittelwerte und Standardabweichungen von Pfadkoeffizienten betrachten (vgl. Wright et al., 2022): - aber: verzerrt dadurch, dass viele Edges auf 0 gesetzt werden!
ind_ests <- read.csv(here("Anwendungs_Workshop/output/indivPathEstimates.csv"))
# kalkulieren Mittelwerte und Standardabweichungen pro Gruppe
ind_ests |>
dplyr::group_by(sub_membership, lhs, rhs) |>
dplyr::summarize(b_mean = round(mean(beta, na.rm = TRUE),3),
b_sd = round(sd(beta, na.rm = TRUE),3)) |>
dplyr::ungroup() |>
tidyr::pivot_wider(names_from = sub_membership,
values_from = c(b_mean, b_sd),
names_prefix = "g") |>
dplyr::select(1:5) |>
head(n = 6L) |>
gt()| lhs | rhs | b_mean_g1 | b_mean_g2 | b_mean_g3 |
|---|---|---|---|---|
| Affiliation | Affiliationlag | 0.184 | 0.138 | 0.211 |
| Affiliation | PosAff | 0.244 | 0.502 | 0.381 |
| Affiliation | PosAfflag | 0.277 | NA | NA |
| Affiliation | Stresslag | -0.302 | NA | NA |
| Dominance | Affiliation | 0.332 | -0.017 | NA |
| Dominance | Dominancelag | 0.072 | 0.078 | 0.040 |
Beispiel Person 2:
Hauptoutput: indivPathEstimates.csv
Punktschätzer & Standardfehler für alle Edges
| file | lhs | op | rhs | beta | se | z | pval | level | sub_membership |
|---|---|---|---|---|---|---|---|---|---|
| subj1 | Dominance | ~ | Dominancelag | 0.169 | 0.109 | 1.542 | 0.123 | group | 2 |
| subj1 | Affiliation | ~ | Affiliationlag | 0.045 | 0.100 | 0.449 | 0.654 | group | 2 |
| subj1 | PosAff | ~ | PosAfflag | 0.089 | 0.092 | 0.965 | 0.334 | group | 2 |
| subj1 | NegAff | ~ | NegAfflag | 0.393 | 0.089 | 4.430 | 0.000 | group | 2 |
| subj1 | Stress | ~ | Stresslag | -0.162 | 0.084 | -1.932 | 0.053 | group | 2 |
| subj1 | Functioning | ~ | Functioninglag | 0.150 | 0.102 | 1.460 | 0.144 | group | 2 |
Latente Variablen
GIMME mit exogenen Variablen (Beltz & Gates, 2017)
CS-GIMME: Confirmatory Subgroups
ms-GIMME, Auswahl mit AICBeispieldaten aus dem Package:
Implementiert in praktischer Online-App
psych::rmssd).graphicalVAR schätzen und mit dem GIMME-Ergebnis vergleichen.